﻿using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Dymetis.Expressions;
using System.Linq.Expressions;
using System.Xml.Linq;

namespace Dymetis.TeamAlert.Expressions
{
    [TestClass]
    public class LogicalExpressionTest
    {
        private readonly Random rand = new Random();

        [TestMethod]
        public void EvaluateDefault_001()
        {
            var a = rand.Next();
            var b = a;

            var expression = LogicalExpression.Parse(String.Format("{0} == {1}", a, b));

            var result = expression.Evaluate();

            Assert.AreEqual(a == b, result);
        }

        [TestMethod]
        public void EvaluateDefault_002()
        {
            var a = rand.Next();
            var b = a;

            var expression = LogicalExpression.Parse(String.Format("{0} != {1}", a, b));

            var result = expression.Evaluate();

            Assert.AreEqual(a != b, result);
        }

        [TestMethod]
        public void EvaluateDefault_003()
        {
            var a = rand.Next();
            var b = a;

            var expression = LogicalExpression.Parse(String.Format("{0} > {1}", a, b));

            var result = expression.Evaluate();

            Assert.AreEqual(a > b, result);
        }

        [TestMethod]
        public void EvaluateDefault_004()
        {
            var a = rand.Next();
            var b = a;

            var expression = LogicalExpression.Parse(String.Format("{0} >= {1}", a, b));

            var result = expression.Evaluate();

            Assert.AreEqual(a >= b, result);
        }

        [TestMethod]
        public void EvaluateDefault_005()
        {
            var a = rand.Next();
            var b = a;

            var expression = LogicalExpression.Parse(String.Format("{0} < {1}", a, b));

            var result = expression.Evaluate();

            Assert.AreEqual(a < b, result);
        }

        [TestMethod]
        public void EvaluateDefault_006()
        {
            var a = rand.Next();
            var b = a;

            var expression = LogicalExpression.Parse(String.Format("{0} <= {1}", a, b));

            var result = expression.Evaluate();

            Assert.AreEqual(a <= b, result);
        }

        [TestMethod]
        public void EvaluateDefault_007()
        {
            var a = rand.Next();
            var b = a;

            var expression = LogicalExpression.Parse(String.Format("$a == {0}", b));

            var result = expression.Evaluate(CreateLookup(Tuple.Create("a", a)));

            Assert.AreEqual(a == b, result);
        }

        [TestMethod]
        public void EvaluateDefault_008()
        {
            var a = rand.Next();
            var b = a;

            var expression = LogicalExpression.Parse("$a == $b");

            var result = expression.Evaluate(CreateLookup(Tuple.Create("a", a), Tuple.Create("b", b)));

            Assert.AreEqual(a == b, result);
        }

        [TestMethod]
        public void EvaluateDefault_009()
        {
            var a = rand.Next();
            var b = a;

            var expression = LogicalExpression.Parse("$a != $b");

            var result = expression.Evaluate(CreateLookup(Tuple.Create("a", a), Tuple.Create("b", b)));

            Assert.AreEqual(a != b, result);
        }

        [TestMethod]
        public void EvaluateDefault_010()
        {
            var a = rand.Next();
            var b = a;

            var expression = LogicalExpression.Parse("$a < $b");

            var result = expression.Evaluate(CreateLookup(Tuple.Create("a", a), Tuple.Create("b", b)));

            Assert.AreEqual(a < b, result);
        }

        [TestMethod]
        public void EvaluateDefault_011()
        {
            var a = rand.Next();
            var b = a;

            var expression = LogicalExpression.Parse("$a <= $b");

            var result = expression.Evaluate(CreateLookup(Tuple.Create("a", a), Tuple.Create("b", b)));

            Assert.AreEqual(a <= b, result);
        }

        [TestMethod]
        public void EvaluateDefault_012()
        {
            var a = rand.Next();
            var b = a;

            var expression = LogicalExpression.Parse("$a > $b");

            var result = expression.Evaluate(CreateLookup(Tuple.Create("a", a), Tuple.Create("b", b)));

            Assert.AreEqual(a > b, result);
        }

        [TestMethod]
        public void EvaluateDefault_013()
        {
            var a = rand.Next();
            var b = a;

            var expression = LogicalExpression.Parse("$a >= $b");

            var result = expression.Evaluate(CreateLookup(Tuple.Create("a", a), Tuple.Create("b", b)));

            Assert.AreEqual(a >= b, result);
        }

        [TestMethod]
        public void EvaluateDefault_014()
        {
            var expression = LogicalExpression.Parse("$a >= $b");

            var result = expression.Evaluate(CreateLookup(Tuple.Create("a", (object)null), Tuple.Create("b", (object)null)));

            Assert.AreEqual(null == null, result);
        }

        [TestMethod]
        public void EvaluateExtendedEventFilter_001()
        {
            var expression = LogicalExpression.Parse(
                "$\"CoreFields/StringFields/Field[ReferenceName='System.WorkItemType']/NewValue\" = Bug " +
                "AND $\"CoreFields/StringFields/Field[ReferenceName='System.AuthorizedAs']/NewValue\" <> \"CoreFields/StringFields/Field[ReferenceName='System.CreatedBy']/NewValue\"",
                LogicalExpressionSyntax.ExtendedEventFilter);

            var doc = new XElement(
                "WorkItemChangedEvent",
                new XElement("CoreFields",
                    new XElement("StringFields",
                        new XElement("Field",
                            new XAttribute("ReferenceName", "System.WorkItemType"),
                            new XElement("NewValue", "Bug")
                        )
                    ),
                    new XElement("StringFields",
                        new XElement("Field",
                            new XAttribute("ReferenceName", "System.AuthorizedAs"),
                            new XElement("NewValue", "Eddie Lin")
                        )
                    ),
                    new XElement("StringFields",
                        new XElement("Field",
                            new XAttribute("ReferenceName", "System.CreatedBy"),
                            new XElement("NewValue", "Eddie Lin")
                        )
                    )
                )
            );

            var result = expression.Evaluate(doc.AsParameterProvider());

            Assert.AreEqual(false, result);
        }

        [TestMethod]
        public void EvaluateExtendedEventFilter_002()
        {
            var expression = LogicalExpression.Parse(
                "$\"CoreFields/StringFields/Field[ReferenceName='System.WorkItemType']/NewValue\" = Bug " +
                "AND $\"CoreFields/StringFields/Field[ReferenceName='System.AuthorizedAs']/NewValue\" <> $\"CoreFields/StringFields/Field[ReferenceName='System.CreatedBy']/NewValue\"",
                LogicalExpressionSyntax.ExtendedEventFilter);

            var doc = new XElement(
                "WorkItemChangedEvent",
                new XElement("CoreFields",
                    new XElement("StringFields",
                        new XElement("Field",
                            new XElement("ReferenceName", "System.WorkItemType"),
                            new XElement("NewValue", "Bug")
                        ),
                        new XElement("Field",
                            new XElement("ReferenceName", "System.AuthorizedAs"),
                            new XElement("NewValue", "Eddie Lin")
                        ),
                        new XElement("Field",
                            new XElement("ReferenceName", "System.CreatedBy"),
                            new XElement("NewValue", "elin")
                        )
                    )
                )
            );

            var result = expression.Evaluate(doc.AsParameterProvider());

            Assert.AreEqual(true, result);
        }

        [TestMethod]
        public void EvaluateExtendedEventFilter_003()
        {
            var expression = LogicalExpression.Parse(
                String.Format("date('{0}') > today - 1", DateTime.Now),
                LogicalExpressionSyntax.ExtendedEventFilter);

            var result = expression.Evaluate();

            Assert.AreEqual(true, result);
        }

        [TestMethod]
        public void EvaluateExtendedEventFilter_004()
        {
            var expression = LogicalExpression.Parse(
                String.Format("date('{0}') < today + 1 and '|abc|' like abc and not(abcedfg match '^bcd')", DateTime.Now),
                LogicalExpressionSyntax.ExtendedEventFilter);

            var result = expression.Evaluate();

            Assert.AreEqual(true, result);
        }

        [TestMethod]
        public void EvaluateExtendedEventFilter_005()
        {
            var expression = LogicalExpression.Parse(
                String.Format("'\\abc\\efg' under '\\efg;\\abc'", DateTime.Now),
                LogicalExpressionSyntax.ExtendedEventFilter);

            var result = expression.Evaluate();

            Assert.AreEqual(true, result);
        }

        [TestMethod]
        public void EvaluateExtendedEventFilter_006()
        {
            var expression = LogicalExpression.Parse(
                String.Format("'\\abc\\efg;\\abd' under '\\efg'", DateTime.Now),
                LogicalExpressionSyntax.ExtendedEventFilter);

            var result = expression.Evaluate();

            Assert.AreEqual(false, result);
        }

        [TestMethod]
        public void EvaluateExtendedEventFilter_101()
        {
            var input = 
                "$DefinitionPath Under '\\DemoProject\\' " + 
                "AND (" + 
                "    $StatusCode LIKE 'Stopped|Failed|PartiallySucceeded' " + 
                "    OR ( " +
                "        $StatusCode = Succeeded " + 
                "        AND $TestStatus <> Succeeded " + 
                "    ) " + 
                ")";
            
            var parameters = CreateLookup(
                Tuple.Create("DefinitionPath", @"\DemoProject\MyApp"),
                Tuple.Create("StatusCode", @"Succeeded"), 
                Tuple.Create("TestStatus", @"Failed")); 
            
            var expression = LogicalExpression.Parse(input, LogicalExpressionSyntax.ExtendedEventFilter); 
            var result = expression.Evaluate(parameters); Assert.IsTrue(result);
        }

        [TestMethod]
        public void EvaluateDefault_100()
        {
            for (int i = 0; i < 100; ++i)
            {
                int count = 100;
                StringBuilder buffer = new StringBuilder();

                var exp = RandomExpresion(buffer, ref count);
                var lambda = Expression.Lambda<Func<bool>>(exp);

                var expression = LogicalExpression.Parse(buffer.ToString());

                Assert.AreEqual(lambda.Compile().DynamicInvoke(), expression.Evaluate(),
                    "Failed to evaluate express {0}, EXP = {1}.", buffer, exp);
            }
        }

        [TestMethod]
        public void EvaluateDefault_101()
        {
            var a = rand.Next();
            var b = rand.Next();

            var expression = LogicalExpression.Parse(String.Format("{0} == {1} OR {1} != {0}", a, b));

            var result = expression.Evaluate();

            Assert.AreEqual(a == b || b != a, result);
        }

        [TestMethod]
        public void EvaluateDefault_102()
        {
            var a = rand.Next();
            var b = rand.Next();

            var expression = LogicalExpression.Parse(String.Format("{0} == {1} OR NOT({1} == {0})", a, b));

            var result = expression.Evaluate();

            Assert.AreEqual(a == b || !(b == a), result);
        }

        [TestMethod]
        public void EvaluateDefault_103()
        {
            var a = rand.Next();
            var b = rand.Next();

            var expression = LogicalExpression.Parse(String.Format("{0} == {0} OR {0} == {1} OR NOT({1} == {0})", a, b));

            var result = expression.Evaluate();

            Assert.AreEqual(a == b || a == b || !(b == a), result);
        }

        [TestMethod]
        public void EvaluateDefault_104()
        {
            var a = rand.Next();
            var b = rand.Next();

            var expression = LogicalExpression.Parse(String.Format("{0} == {0} OR {0} == {1} OR NOT({1} == {0} AND {1} == {0})", a, b));

            var result = expression.Evaluate();

            Assert.AreEqual(a == b || a == b || !(b == a && b == a), result);
        }

        private static IValueLookup CreateLookup<T>(params Tuple<string, T>[] parameters)
        {
            var dict = new Dictionary<string, object>();

            foreach (var p in parameters)
            {
                dict.Add(p.Item1, p.Item2);
            }

            return dict.AsParameterProvider();
        }

        public Expression RandomExpresion(StringBuilder expBuffer, ref int count)
        {
            Expression exp = null;
            if (count > 0)
            {
                var a = rand.Next(10);
                var b = rand.Next(10);

                count--;

                expBuffer.Append("(");

                if (rand.Next(2) == 0)
                {
                    switch (rand.Next(6))
                    {
                        case 0:
                            expBuffer
                                .Append(a)
                                .Append(" == ")
                                .Append(b);
                            exp = Expression.Equal(
                                Expression.Constant(a, typeof(int)),
                                Expression.Constant(b, typeof(int))
                            );
                            break;
                        case 1:
                            expBuffer
                                .Append(a)
                                .Append(" != ")
                                .Append(b);
                            exp = Expression.NotEqual(
                                Expression.Constant(a, typeof(int)),
                                Expression.Constant(b, typeof(int))
                            );
                            break;
                        case 2:
                            expBuffer
                                .Append(a)
                                .Append(" < ")
                                .Append(b);
                            exp = Expression.LessThan(
                                Expression.Constant(a, typeof(int)),
                                Expression.Constant(b, typeof(int))
                            );
                            break;
                        case 3:
                            expBuffer
                                .Append(a)
                                .Append(" <= ")
                                .Append(b);
                            exp = Expression.LessThanOrEqual(
                                Expression.Constant(a, typeof(int)),
                                Expression.Constant(b, typeof(int))
                            );
                            break;
                        case 4:
                            expBuffer
                                .Append(a)
                                .Append(" > ")
                                .Append(b);
                            exp = Expression.GreaterThan(
                                Expression.Constant(a, typeof(int)),
                                Expression.Constant(b, typeof(int))
                            );
                            break;
                        case 5:
                            expBuffer
                                .Append(a)
                                .Append(" >= ")
                                .Append(b);
                            exp = Expression.GreaterThanOrEqual(
                                Expression.Constant(a, typeof(int)),
                                Expression.Constant(b, typeof(int))
                            );
                            break;
                    }
                }
                else
                {
                    if (rand.Next(3) == 0)
                    {
                        expBuffer.Append(" NOT ");
                        var inner = RandomExpresion(expBuffer, ref count);

                        exp = Expression.Not(inner);
                    }
                    else
                    {
                        var opt = rand.Next(2);
                        var left = RandomExpresion(expBuffer, ref count);

                        if (opt == 0)
                        {
                            expBuffer.Append(" AND ");
                        }
                        else
                        {
                            expBuffer.Append(" OR ");
                        }

                        var right = RandomExpresion(expBuffer, ref count);

                        if (opt == 0)
                        {
                            exp = Expression.And(left, right);
                        }
                        else
                        {
                            exp = Expression.Or(left, right);
                        }
                    }
                }

                expBuffer.Append(")");
            }

            if (exp == null)
            {
                expBuffer.Append("1 == 1");
                return Expression.Equal(
                    Expression.Constant(1, typeof(int)),
                    Expression.Constant(1, typeof(int))
                );
            }

            return exp;
        }

    }
}
